package org.amuthu.expression;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;
import org.amuthu.expression.binary.ArithmeticExprType;
import org.amuthu.expression.binary.ComparisionExprType;
import org.amuthu.expression.binary.LogicExprType;
import org.amuthu.expression.binary.LogicExpression;
import org.amuthu.expression.binary.ComparisionExpression;
import org.amuthu.expression.binary.ArithmeticExpression;
import org.amuthu.expression.function.FunctionExpression;
import org.amuthu.expression.terminal.NumberExpression;
import org.amuthu.expression.terminal.QuoteExprType;
import org.amuthu.expression.terminal.QuoteExpression;
import org.amuthu.expression.unary.UnaryExprType;
import org.amuthu.expression.unary.UnaryExpression;
import org.amuthu.parser.AmuthuLexer;

import org.antlr.runtime.tree.CommonTree;

/**
 * Create an executable expression from the given token and arguments.
 *
 * @author Andrew Leppard
 */
public class ExpressionFactory
{
   private final static String format = "0.000000#";
   private final static DecimalFormat decimalFormat = new DecimalFormat(format, new DecimalFormatSymbols(Locale.ENGLISH));

   // Cannot create an instance of this class
   private ExpressionFactory()
   {
      // not possible
      assert false;
   }

      public static double parseDouble(String doubleText) throws NumberFormatException
   {
      double retValue = 0;
      try
      {
         Number num = decimalFormat.parse(doubleText);
         if (num == null)
         {
            throw new ParseException(
                    "AbstractExpression - parseDouble - null Error", 0);
         }
         retValue = decimalFormat.parse(doubleText).doubleValue();
      }
      catch (ParseException e)
      {
         throw new NumberFormatException();
      }
      return retValue;
   }

   public static int parseInt(String intText) throws NumberFormatException, ParseException
   {
      int retValue = 0;
      try
      {
         Number num = decimalFormat.parse(intText);
         if (num == null)
         {
            throw new ParseException("AbstractExpression - parseInt - null Error", 0);
         }
         retValue = decimalFormat.parse(intText).intValue();
      }
      catch (ParseException e)
      {
         throw new NumberFormatException();
      }
      return retValue;
   }

   private static QuoteExprType getQuoteExprType(String quote)
   {
      if (quote.equals("open"))
      {
         return QuoteExprType.DAY_OPEN;
      }
      else if (quote.equals("close"))
      {
         return QuoteExprType.DAY_CLOSE;
      }
      else if (quote.equals("high"))
      {
         return QuoteExprType.DAY_HIGH;
      }
      else if (quote.equals("low"))
      {
         return QuoteExprType.DAY_LOW;
      }
      else
      {
         return QuoteExprType.DAY_VOLUME;
      }
   }

   protected static ComparisionExpression createComparisonExpr(CommonTree ast) throws NumberFormatException, ParseException
   {
      Expression left = create((CommonTree) ast.getChild(0));
      Expression right = create((CommonTree) ast.getChild(1));

      ComparisionExprType exprType;

      switch (ast.getType())
      {
         case AmuthuLexer.EQUALS:
            exprType = ComparisionExprType.EQUALTO;
            break;
         case AmuthuLexer.NOT_EQUALS:
            exprType = ComparisionExprType.NOT_EQUALTO;
            break;
         case AmuthuLexer.LESSTHAN:
            exprType = ComparisionExprType.LESS_THAN;
            break;
         case AmuthuLexer.LESSTHAN_EQ:
            exprType = ComparisionExprType.LESS_THAN_EQUAL;
            break;
         case AmuthuLexer.GREATERTHAN:
            exprType = ComparisionExprType.GREATER_THAN;
            break;
         case AmuthuLexer.GREATERTHAN_EQ:
            exprType = ComparisionExprType.GREATER_THAN_EQUAL;
            break;
         default:
            throw new AssertionError();
      }

      return new ComparisionExpression(exprType, left, right);
   }

   protected static ArithmeticExpression createArithmeticExpr(CommonTree ast) throws NumberFormatException, ParseException
   {
      Expression left = create((CommonTree) ast.getChild(0));
      Expression right = create((CommonTree) ast.getChild(1));

      ArithmeticExprType exprType;

      switch (ast.getType())
      {
         case AmuthuLexer.PLUS:
            exprType = ArithmeticExprType.ADD;
            break;

         case AmuthuLexer.MINUS:
            exprType = ArithmeticExprType.SUBTRACT;
            break;

         case AmuthuLexer.MULTIPLY:
            exprType = ArithmeticExprType.MULTIPLY;
            break;

         case AmuthuLexer.DIVIDE:
            exprType = ArithmeticExprType.DIVIDE;
            break;

         case AmuthuLexer.MODULO:
            exprType = ArithmeticExprType.MODULO;
            break;

         case AmuthuLexer.POWER:
            exprType = ArithmeticExprType.POWER;
            break;

         default:
            throw new AssertionError();
      }

      return new ArithmeticExpression(exprType, left, right);
   }

   public static Expression create(CommonTree ast) throws NumberFormatException, ParseException
   {
      switch (ast.getType())
      {
         case AmuthuLexer.INTEGER:
            return new NumberExpression(parseInt(ast.getText()));

         case AmuthuLexer.DOUBLE:
            return new NumberExpression(parseDouble(ast.getText()));

         case AmuthuLexer.BOOLEAN:
            return new NumberExpression(Boolean.parseBoolean(ast.getText()));

         case AmuthuLexer.PLUS:
         case AmuthuLexer.MINUS:
         case AmuthuLexer.MULTIPLY:
         case AmuthuLexer.DIVIDE:
         case AmuthuLexer.MODULO:
         case AmuthuLexer.POWER:
            return createArithmeticExpr(ast);

         case AmuthuLexer.AND:
            return new LogicExpression(LogicExprType.AND, create((CommonTree) ast.getChild(0)), create((CommonTree) ast.getChild(1)));

         case AmuthuLexer.OR:
            return new LogicExpression(LogicExprType.OR, create((CommonTree) ast.getChild(0)), create((CommonTree) ast.getChild(1)));

         case AmuthuLexer.EQUALS:
         case AmuthuLexer.NOT_EQUALS:
         case AmuthuLexer.LESSTHAN:
         case AmuthuLexer.LESSTHAN_EQ:
         case AmuthuLexer.GREATERTHAN:
         case AmuthuLexer.GREATERTHAN_EQ:
            return createComparisonExpr(ast);

         case AmuthuLexer.NOT:
            return new UnaryExpression(UnaryExprType.NOT, create((CommonTree) ast.getChild(0)));

         case AmuthuLexer.UNARY_MINUS:
            return new UnaryExpression(UnaryExprType.NEGATE, create((CommonTree) ast.getChild(0)));

         case AmuthuLexer.QUOTE:
            return new QuoteExpression(getQuoteExprType(ast.getText()));

         case AmuthuLexer.IDENTIFIER:
         {
            Expression[] arguments = new Expression[ast.getChildCount()];

            for (int i = 0; i < ast.getChildCount(); i++)
            {
               arguments[i] = create((CommonTree) ast.getChild(i));
            }

            return new FunctionExpression(ast.getText(), arguments, ast.getChildCount());

         }

         default:
            return null;
      }
   }

}
